package com.anji.plus.gaea.security.plus.modules.menu.service.impl;

import com.anji.plus.gaea.bean.TreeNode;
import com.anji.plus.gaea.cache.CacheHelper;
import com.anji.plus.gaea.constant.Enabled;
import com.anji.plus.gaea.constant.GaeaConstant;
import com.anji.plus.gaea.constant.GaeaKeyConstant;
import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper;
import com.anji.plus.gaea.holder.UserContentHolder;
import com.anji.plus.gaea.security.plus.modules.authority.dao.GaeaAuthorityMapper;
import com.anji.plus.gaea.security.plus.modules.authority.dao.entity.GaeaAuthority;
import com.anji.plus.gaea.security.plus.modules.menu.controller.dto.GaeaLeftMenuDTO;
import com.anji.plus.gaea.security.plus.modules.menu.controller.dto.GaeaMenuDTO;
import com.anji.plus.gaea.security.plus.modules.menu.controller.param.GaeaMenuParam;
import com.anji.plus.gaea.security.plus.modules.menu.dao.GaeaMenuMapper;
import com.anji.plus.gaea.security.plus.modules.menu.dao.entity.GaeaMenu;
import com.anji.plus.gaea.security.plus.modules.menu.service.GaeaMenuService;
import com.anji.plus.gaea.security.plus.modules.role.dao.GaeaRoleMenuAuthorityMapper;
import com.anji.plus.gaea.security.plus.modules.role.dao.entity.GaeaRoleMenuAuthority;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 菜单表(GaeaMenu)ServiceImpl
 *
 * @author lr
 * @since 2021-02-02 13:36:43
 */
@Service
public class GaeaMenuServiceImpl implements GaeaMenuService {

    @Autowired
    private GaeaMenuMapper gaeaMenuMapper;

    @Autowired
    private GaeaRoleMenuAuthorityMapper gaeaRoleMenuAuthorityMapper;

    @Autowired
    private GaeaAuthorityMapper gaeaAuthorityMapper;

    /**
     * 菜单字典项
     */
    private final static String MENU_LANGUAGE = "MENU_LANGUAGE";

    private Map<String, String> manuDictMap = new HashMap<>();

    @Override
    public GaeaBaseMapper<GaeaMenu> getMapper() {
        return gaeaMenuMapper;
    }

    @Autowired
    private CacheHelper cacheHelper;


    /**
     * 当菜单编码不为空时，要查出当前菜单的自菜单
     * @param param   查询参数
     * @param queryWrapper 基本查询条件
     * @return
     */
    @Override
    public Wrapper<GaeaMenu> extensionWrapper(GaeaMenuParam param, QueryWrapper<GaeaMenu> queryWrapper) {
        if (StringUtils.isNotBlank(param.getCode())) {
            queryWrapper.and(wrapper -> wrapper.or().lambda().eq(GaeaMenu::getParentCode, param.getCode()).or().eq(GaeaMenu::getMenuCode, param.getCode()));
        }
        return queryWrapper;
    }

    /**
     * 获取角色对应的菜单
     *
     * @param roles
     * @return
     */
    @Override
    public List<GaeaLeftMenuDTO> getMenus(List<String> roles) {

        manuDictMap = cacheHelper.hashGet(GaeaKeyConstant.DICT_PREFIX + LocaleContextHolder.getLocale().getLanguage() + GaeaConstant.REDIS_SPLIT + MENU_LANGUAGE);

        if (CollectionUtils.isEmpty(roles)) {
            return new ArrayList<>();
        }

        //菜单编码与其下的按钮
        Map<String, Set<String>> menuAuthMap = new HashMap<>();
        LambdaQueryWrapper<GaeaMenu> resourceQueryWrapper = Wrappers.<GaeaMenu>lambdaQuery()
                .eq(GaeaMenu::getSysCode, UserContentHolder.getContext().getSysCode())
                .eq(GaeaMenu::getEnabled, Enabled.YES.getValue())
                .orderByAsc(GaeaMenu::getSort);;
        //判断是否有超管角色
        if (roles.contains(GaeaConstant.SUPER_ADMIN_ROLE)) {
            List<GaeaAuthority> gaeaAuthorities = gaeaAuthorityMapper.selectList(Wrappers.emptyWrapper());
            menuAuthMap.putAll(gaeaAuthorities.stream().collect(Collectors.groupingBy(GaeaAuthority::getMenuCode, Collectors.mapping(GaeaAuthority::getAuthCode, Collectors.toSet()))));
        } else {
            //查询指定角色对应的菜单按钮
            LambdaQueryWrapper<GaeaRoleMenuAuthority> queryWrapper = Wrappers.<GaeaRoleMenuAuthority>lambdaQuery()
                    .eq(GaeaRoleMenuAuthority::getOrgCode, UserContentHolder.getOrgCode())
                    .in(GaeaRoleMenuAuthority::getRoleCode, roles);
            //查询菜单
            Set<String> menuCodes = getMenuCodes(UserContentHolder.getOrgCode(), roles.toArray(new String[]{}));
            //菜单编码与其下的按钮
            menuAuthMap.putAll(gaeaRoleMenuAuthorityMapper.selectList(queryWrapper)
                    .stream().filter(authority -> menuCodes.contains(authority.getMenuCode()))
                    .collect(Collectors.groupingBy(GaeaRoleMenuAuthority::getMenuCode,
                            Collectors.mapping(GaeaRoleMenuAuthority::getAuthCode, Collectors.toSet()))));
            if (CollectionUtils.isEmpty(menuAuthMap)) {
                return new ArrayList<>();
            }

            //获取当前用户所有菜单
            resourceQueryWrapper.in(GaeaMenu::getMenuCode, menuAuthMap.keySet());

        }

        //当前用户所有的叶子菜单
        List<GaeaMenu> leafMenuList = gaeaMenuMapper.selectList(resourceQueryWrapper);
        if (CollectionUtils.isEmpty(leafMenuList)) {
            return new ArrayList<>();
        }


        //查询所有菜单
        List<GaeaMenu> allMenus = gaeaMenuMapper.selectList(Wrappers.emptyWrapper());

        //菜单编码与菜单的对应关系
        Map<String, GaeaLeftMenuDTO> menuMap = allMenus.stream().collect(Collectors.toMap(GaeaMenu::getMenuCode, gaeaMenu -> {
            GaeaLeftMenuDTO dto = new GaeaLeftMenuDTO();
            BeanUtils.copyProperties(gaeaMenu, dto);
            dto.setName(gaeaMenu.getMenuCode());
            dto.setChildren(new ArrayList<>());
            return dto;
        }));

        //转换
        List<GaeaLeftMenuDTO> leafMenus = leafMenuList.stream().map(gaeaMenu -> {
            GaeaLeftMenuDTO gaeaLeftMenuDTO = menuMap.get(gaeaMenu.getMenuCode());
            gaeaLeftMenuDTO.setName(gaeaMenu.getMenuCode());
            setDtoMeta(gaeaLeftMenuDTO, gaeaMenu.getMenuCode());

            gaeaLeftMenuDTO.setPermission(menuAuthMap.get(gaeaLeftMenuDTO.getMenuCode()));
            return gaeaLeftMenuDTO;
        }).collect(Collectors.toList());


        //按父菜单Code分组

        Map<String, List<GaeaLeftMenuDTO>> leafParentMap = leafMenus.stream()
                .filter(gaeaLeftMenuDTO -> StringUtils.isNotBlank(gaeaLeftMenuDTO.getParentCode()))
                .collect(Collectors.groupingBy(GaeaLeftMenuDTO::getParentCode));

        //只有一级叶子节点，即父节点为空
        List<GaeaLeftMenuDTO> hasNoParentList = leafMenus.stream()
                .filter(gaeaLeftMenuDTO -> StringUtils.isBlank(gaeaLeftMenuDTO.getParentCode())).collect(Collectors.toList());


        //当有父目录时
        List<GaeaLeftMenuDTO> menuResult = leafParentMap.entrySet().stream()
                .filter(entry -> menuMap.get(entry.getKey()) != null)
                .map(entry -> {
                    GaeaLeftMenuDTO gaeaLeftMenuDTO = menuMap.get(entry.getKey());
                    gaeaLeftMenuDTO.setName(gaeaLeftMenuDTO.getMenuCode());
                    gaeaLeftMenuDTO.setChildren(entry.getValue());
                    //设置元数据
                    setDtoMeta(gaeaLeftMenuDTO, gaeaLeftMenuDTO.getMenuCode());
                    return setChild(gaeaLeftMenuDTO, menuMap);
                }).distinct().collect(Collectors.toList());

        if (!CollectionUtils.isEmpty(hasNoParentList)) {
            menuResult.addAll(hasNoParentList);
        }
        //当没有父目录时
        return menuResult.stream().distinct().sorted(Comparator.comparing(GaeaLeftMenuDTO::getSort)).collect(Collectors.toList());
    }

    private void setDtoMeta(GaeaLeftMenuDTO gaeaMenuDTO, String menuCode) {
        Map<String, String> meta = new HashMap<>(2);
        String title = manuDictMap.get(menuCode);

        if (StringUtils.isBlank(title)) {
            title = menuCode;
        }
        meta.put("title", title);
        meta.put("icon", gaeaMenuDTO.getMenuIcon());
        gaeaMenuDTO.setMeta(meta);
    }


    /**
     * 递归寻找父菜单
     *
     * @param gaeaMenuDTO
     * @param menuMap
     * @return
     */
    public GaeaLeftMenuDTO setChild(GaeaLeftMenuDTO gaeaMenuDTO, Map<String, GaeaLeftMenuDTO> menuMap) {

        //当没有父菜单code时，递归结束
        if (StringUtils.isEmpty(gaeaMenuDTO.getParentCode())) {
            return gaeaMenuDTO;
        }
        GaeaLeftMenuDTO dto = menuMap.get(gaeaMenuDTO.getParentCode());

        dto.setName(dto.getMenuCode());
        setDtoMeta(dto, dto.getMenuCode());
        return setChild(dto, menuMap);
    }

    /**
     * 获取所有菜单权限树
     *
     * @return
     */
    @Override
    public List<TreeNode> getTree() {

        //获取所有菜单
        LambdaQueryWrapper<GaeaMenu> resourceQueryWrapper = Wrappers.<GaeaMenu>lambdaQuery()
                .eq(GaeaMenu::getEnabled, Enabled.YES.getValue())
                .orderByAsc(GaeaMenu::getSort);


        List<GaeaMenu> allResources = gaeaMenuMapper.selectList(resourceQueryWrapper);
        List<GaeaAuthority> gaeaAuthorities = gaeaAuthorityMapper.selectList(Wrappers.emptyWrapper());

        Map<String, String> authorityMap = gaeaAuthorities.stream().filter(s -> StringUtils.isNotBlank(s.getAuthName()))
                .collect(Collectors.toMap(GaeaAuthority::getAuthCode, GaeaAuthority::getAuthName, (v1, v2) -> v2));

        //authCode去重

        //给菜单分组
        Map<String, List<TreeNode>> menuAuthMap = gaeaAuthorities.stream()
                .collect(Collectors.groupingBy(GaeaAuthority::getMenuCode, Collectors.mapping(authority -> {

                    String key = authority.getAuthCode();
                    TreeNode treeNode = new TreeNode();
                    treeNode.setId(key);
                    String authCode = authority.getAuthCode();
                    String authName = authorityMap.get(authCode);
                    treeNode.setLabel(StringUtils.isNotBlank(authName) ? authName : authCode);
                    return treeNode;
                }, Collectors.collectingAndThen(Collectors.toCollection(()-> new TreeSet<>(Comparator.comparing(TreeNode::getId))),ArrayList::new))));

        //Collectors.toCollection(TreeSet::new));

        //转换DTO,用于返回
        List<GaeaMenuDTO> gaeaAuthMenuDTOS = allResources.stream().map(gaeaMenu -> {
            GaeaMenuDTO dto = new GaeaMenuDTO();
            BeanUtils.copyProperties(gaeaMenu, dto);
            return dto;
        }).collect(Collectors.toList());

        //一级菜单（没有父菜单）
        List<GaeaMenuDTO> rootResources = gaeaAuthMenuDTOS.stream().filter(resource -> StringUtils.isBlank(resource.getParentCode())).collect(Collectors.toList());

        //遍及一级菜单，找出它的下级菜单
        List<TreeNode> result = setTreeNode(gaeaAuthMenuDTOS, rootResources, menuAuthMap);

        //除了菜单对应的权限都放在其他下面
        List<String> menuCodes = allResources.stream().map(GaeaMenu::getMenuCode).collect(Collectors.toList());
        List<GaeaAuthority> noMenuCodes = gaeaAuthorities.stream().filter(gaeaAuthority -> !menuCodes.contains(gaeaAuthority.getMenuCode())).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(noMenuCodes)) {
            TreeNode other = new TreeNode();
            other.setId("other");
            other.setLabel("其他");
            List<TreeNode> collect = noMenuCodes.stream().map(gaeaAuthority -> {
                String key = gaeaAuthority.getAuthCode();
                TreeNode treeNode = new TreeNode();
                treeNode.setId(key);
                String authCode = gaeaAuthority.getAuthCode();
                String authName = authorityMap.get(authCode);
                treeNode.setLabel(StringUtils.isNotBlank(authName) ? authName : authCode);
                return treeNode;
            }).collect(Collectors.collectingAndThen(Collectors.toCollection(()-> new TreeSet<>(Comparator.comparing(TreeNode::getId))),ArrayList::new));
            other.setChildren(collect);
            result.add(other);
        }

        return result;
    }

    /**
     * 设置子节点
     *
     * @param gaeaMenuDTOS
     * @param rootResources
     * @return
     */
    private List<TreeNode> setTreeNode(List<GaeaMenuDTO> gaeaMenuDTOS, List<GaeaMenuDTO> rootResources, Map<String, List<TreeNode>> menuAuthMap) {
        return rootResources.stream().map(resource -> {
            TreeNode node = new TreeNode();
            node.setId(resource.getMenuCode());
            node.setLabel(resource.getMenuName());
            //树子集合
            List<TreeNode> treeResult = new ArrayList<>();
            //菜单对应的权限
            List<TreeNode> authTreeNodes = menuAuthMap.get(resource.getMenuCode());
            if (!CollectionUtils.isEmpty(authTreeNodes)) {
                treeResult.addAll(authTreeNodes);
            }

            //菜单子菜单
            List<TreeNode> subResourcesTemp = getSubResources(resource.getMenuCode(), gaeaMenuDTOS, menuAuthMap);
            if (!CollectionUtils.isEmpty(subResourcesTemp)) {
                treeResult.addAll(subResourcesTemp);
            }

            if (!CollectionUtils.isEmpty(treeResult)) {
                node.setChildren(treeResult);
            }
            return node;
        }).collect(Collectors.toList());
    }

    /**
     * 递归
     *
     * @param menuCode
     * @param resources
     * @return
     */
    public List<TreeNode> getSubResources(String menuCode, List<GaeaMenuDTO> resources, Map<String, List<TreeNode>> menuAuthMap) {
        List<GaeaMenuDTO> subResources = new ArrayList<>();
        resources.stream().forEach(resource -> {
            //当前code的下级菜单
            if (StringUtils.equals(menuCode, resource.getParentCode())) {
                subResources.add(resource);
            }
        });

        if (subResources.isEmpty()) {
            return new ArrayList<>();
        }

        //子菜单
        List<TreeNode> result = setTreeNode(resources, subResources, menuAuthMap);
        return result;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveMenuAuthorities(String menuCode, List<String> authorities) {


        LambdaUpdateWrapper<GaeaAuthority> updateWrapper = Wrappers.lambdaUpdate();

        updateWrapper.in(GaeaAuthority::getAuthCode, authorities);
        updateWrapper.set(GaeaAuthority::getMenuCode, menuCode);
        return gaeaAuthorityMapper.update(null, updateWrapper) > 0;
    }


    @Override
    public List<String> menuOrgAuthorities(String menuCode) {
        LambdaQueryWrapper<GaeaAuthority> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(GaeaAuthority::getMenuCode, menuCode);
        List<GaeaAuthority> gaeaMenuAuthorities = gaeaAuthorityMapper.selectList(wrapper);
        return gaeaMenuAuthorities.stream().map(GaeaAuthority::getAuthCode).collect(Collectors.toList());
    }

    @Override
    public List<TreeNode> menuTree() {

        //所有菜单
        List<GaeaMenu> gaeaMenus = gaeaMenuMapper.selectList(Wrappers.emptyWrapper());

        LambdaQueryWrapper<GaeaMenu> wrapper = Wrappers.lambdaQuery();
        wrapper.isNull(GaeaMenu::getParentCode).or().eq(GaeaMenu::getParentCode, "");
        //root目录
        List<GaeaMenu> rootMenus = gaeaMenuMapper.selectList(wrapper);

        List<TreeNode> result = rootMenus.stream().map(rootMenu -> {
            TreeNode treeNode = new TreeNode();
            treeNode.setId(rootMenu.getMenuCode());
            treeNode.setLabel(rootMenu.getMenuName());
            treeNode.setChildren(new ArrayList<>());
            //设置子菜单
            setOrgChildren(treeNode, gaeaMenus);
            return treeNode;
        }).collect(Collectors.toList());

        return result;
    }

    /**
     * 设置子菜单
     * @param treeNode
     * @param gaeaMenus
     */
    private void setOrgChildren(TreeNode treeNode, List<GaeaMenu> gaeaMenus) {

        gaeaMenus.stream()
                .filter(gaeaMenu -> StringUtils.equals(treeNode.getId(), gaeaMenu.getParentCode()))
                .forEach(gaeaMenu -> {
                    TreeNode treeNodeTemp = new TreeNode();
                    treeNodeTemp.setId(gaeaMenu.getMenuCode());
                    treeNodeTemp.setLabel(gaeaMenu.getMenuName());
                    treeNodeTemp.setChildren(new ArrayList<>());
                    //设置下级菜单
                    setOrgChildren(treeNodeTemp, gaeaMenus);
                    treeNode.getChildren().add(treeNodeTemp);
                });
    }


    /**
     * 获取指定角色的菜单编码
     * @return
     */
    @Override
    public Set<String> getMenuCodes(String orgCode, String... roleCode) {
        LambdaQueryWrapper<GaeaRoleMenuAuthority> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(GaeaRoleMenuAuthority::getOrgCode, orgCode);
        wrapper.in(GaeaRoleMenuAuthority::getRoleCode, roleCode);
        wrapper.eq(GaeaRoleMenuAuthority::getIsMenu, Enabled.YES.getValue());
        return gaeaRoleMenuAuthorityMapper.selectList(wrapper).stream().map(GaeaRoleMenuAuthority::getMenuCode).collect(Collectors.toSet());
    }
}
